package iredis

import (
	"context"
	"errors"
	"fmt"
	log "github.com/sirupsen/logrus"
	"strconv"
	"time"
)

import (
	"github.com/go-redis/redis/v8"
)

const ID_TIME_LAYOUT = "20060102150405.000"

var redis_lua = `
-- need redis 3.2+
redis.replicate_commands();
local prefix = 'idgen:';
local partitionCount = 4096;
local startStep = 0;
local tag = KEYS[1];
-- if user do not pass shardId, default partition is 0.
local code = KEYS[2]
local now = redis.call('TIME');
local miliSecondKey = prefix .. tag .. ':' .. code .. '_' .. now[1] .. '_' .. math.floor(now[2] / 1000);
local count;
--repeat
count = tonumber(redis.call('INCR', miliSecondKey));
--if count > (1024 - step) then
--    now = redis.call('TIME');
--    miliSecondKey = prefix .. tag .. '_' .. code .. '_' .. now[1] .. '_' .. math.floor(now[2] / 1000);
--end
--until count <= (1024 - step)
redis.call('PEXPIRE', miliSecondKey, 10);
-- second, microSecond, partition, seq
return { miliSecondKey, tonumber(now[1]), tonumber(now[2]), code, count }
`

//code:6,time:14+3,incrid:4
//000012.20190104145814.883.0001
//
// RedisIdGenerator struct
type RedisIdGenerator struct {
	client  *redis.ClusterClient
	luahash string
	fn      func(seconds, microsecond, id int64, tableName string, code int) string
}

// NewIDGenerator construct
func NewIDGenerator(client *redis.ClusterClient) *RedisIdGenerator {
	cmd := client.ScriptLoad(context.Background(), redis_lua)
	if cmd.Err() != nil {
		panic(cmd.Err())
	}
	hash := cmd.Val()
	return &RedisIdGenerator{client: client, luahash: hash, fn: func(seconds, microsecond, id int64, tableName string, code int) string {
		t := time.Unix(seconds, microsecond*1000)
		timeSep := t.Format(ID_TIME_LAYOUT)
		s := fmt.Sprintf("%.6d%s%s%.4d", code, timeSep[:14], timeSep[15:], id)
		log.Debug(len(s), ": ", s)
		return s
	}}
}

// Next func
func (idGen *RedisIdGenerator) Next(tableName string, code int) (string, error) {
	seconds, microsecond, id, err := idGen.next(tableName, code)
	if err != nil {
		return "", err
	}
	s := idGen.fn(seconds, microsecond, id, tableName, code)
	return s, nil
}

// Next func
func (idGen *RedisIdGenerator) NextIncr(tableName string, code int) (int64, error) {
	_, _, id, err := idGen.next(tableName, code)
	if err != nil {
		return 0, err
	}
	return id, nil
}

// Next func
func (idGen *RedisIdGenerator) next(tableName string, code int) (seconds int64, microsecond int64, id int64, err error) {
	strCode := fmt.Sprintf("%.6d", code)
	if len(strCode) > 6 {
		return 0, 0, 0, errors.New("param code length less than 6: " + strCode + ",len= " + strconv.Itoa(len(strCode)))
	}
	cmd := idGen.client.EvalSha(context.Background(), idGen.luahash, []string{tableName, strCode})
	rs, err := cmd.Result()
	if err != nil {
		log.Error(err)
		return 0, 0, 0, err
	}
	log.Debug(rs)
	vals := rs.([]interface{})
	seconds = vals[1].(int64)
	microsecond = vals[2].(int64)
	id = vals[4].(int64)
	return seconds, microsecond, id, nil
}

// ParseID method
//code:6,time:14+3,incrid:4
//000012.20190104145814.883.0001
//
func ParseStringId(id string) (code int, seconds int64, microsecond int64, incrId int64, err error) {
	//000012.20190104145814.883.0001
	codeStr := id[:6]
	code, err = strconv.Atoi(codeStr)
	if err != nil {
		return 0, 0, 0, 0, err
	}
	tim := id[6:20] + "." + id[20:23]
	incrIdStr := id[23:]
	//fmt.Println("xxx: ", code, tim, incrIdStr)
	incrIdInt, err := strconv.Atoi(incrIdStr)
	if err != nil {
		return code, 0, 0, 0, err
	}
	incrId = int64(incrIdInt)
	t, err := time.Parse(ID_TIME_LAYOUT, tim)
	if err != nil {
		return code, 0, 0, incrId, err
	}
	seconds = t.Unix()
	microsecond = int64(t.Nanosecond() / 1000)

	return code, seconds, microsecond, incrId, err
}
